package sparrow.etl.core.util.tar;

import sparrow.etl.core.exception.InvalidHeaderException;

/**
 *
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2004</p>
 * <p>Company: </p>
 * @author not attributable
 * @version 1.0
 */
public
    class TarHeader
    extends Object
    implements Cloneable {
  /**
   * The length of the name field in a header buffer.
   */
  public static final int NAMELEN = 100;
  /**
   * The offset of the name field in a header buffer.
   */
  public static final int NAMEOFFSET = 0;
  /**
   * The length of the name prefix field in a header buffer.
   */
  public static final int PREFIXLEN = 155;
  /**
   * The offset of the name prefix field in a header buffer.
   */
  public static final int PREFIXOFFSET = 345;
  /**
   * The length of the mode field in a header buffer.
   */
  public static final int MODELEN = 8;
  /**
   * The length of the user id field in a header buffer.
   */
  public static final int UIDLEN = 8;
  /**
   * The length of the group id field in a header buffer.
   */
  public static final int GIDLEN = 8;
  /**
   * The length of the checksum field in a header buffer.
   */
  public static final int CHKSUMLEN = 8;
  /**
   * The length of the size field in a header buffer.
   */
  public static final int SIZELEN = 12;
  /**
   * The length of the magic field in a header buffer.
   */
  public static final int MAGICLEN = 8;
  /**
   * The length of the modification time field in a header buffer.
   */
  public static final int MODTIMELEN = 12;
  /**
   * The length of the user name field in a header buffer.
   */
  public static final int UNAMELEN = 32;
  /**
   * The length of the group name field in a header buffer.
   */
  public static final int GNAMELEN = 32;
  /**
   * The length of the devices field in a header buffer.
   */
  public static final int DEVLEN = 8;

  /**
   * LF_ constants represent the "link flag" of an entry, or more commonly,
   * the "entry type". This is the "old way" of indicating a normal file.
   */
  public static final byte LF_OLDNORM = 0;
  /**
   * Normal file type.
   */
  public static final byte LF_NORMAL = (byte) '0';
  /**
   * Link file type.
   */
  public static final byte LF_LINK = (byte) '1';
  /**
   * Symbolic link file type.
   */
  public static final byte LF_SYMLINK = (byte) '2';
  /**
   * Character device file type.
   */
  public static final byte LF_CHR = (byte) '3';
  /**
   * Block device file type.
   */
  public static final byte LF_BLK = (byte) '4';
  /**
   * Directory file type.
   */
  public static final byte LF_DIR = (byte) '5';
  /**
   * FIFO (pipe) file type.
   */
  public static final byte LF_FIFO = (byte) '6';
  /**
   * Contiguous file type.
   */
  public static final byte LF_CONTIG = (byte) '7';

  /**
   * The magic tag representing a POSIX tar archive.
   */
  public static final String TMAGIC = "ustar";

  /**
   * The magic tag representing a GNU tar archive.
   */
  public static final String GNU_TMAGIC = "ustar  ";

  /**
   * The entry's name.
   */
  public StringBuffer name;
  /**
   * The entry's permission mode.
   */
  public int mode;
  /**
   * The entry's user id.
   */
  public int userId;
  /**
   * The entry's group id.
   */
  public int groupId;
  /**
   * The entry's size.
   */
  public long size;
  /**
   * The entry's modification time.
   */
  public long modTime;
  /**
   * The entry's checksum.
   */
  public int checkSum;
  /**
   * The entry's link flag.
   */
  public byte linkFlag;
  /**
   * The entry's link name.
   */
  public StringBuffer linkName;
  /**
   * The entry's magic tag.
   */
  public StringBuffer magic;
  /**
   * The entry's user name.
   */
  public StringBuffer userName;
  /**
   * The entry's group name.
   */
  public StringBuffer groupName;
  /**
   * The entry's major device number.
   */
  public int devMajor;
  /**
   * The entry's minor device number.
   */
  public int devMinor;

  public
      TarHeader() {
    this.magic = new StringBuffer(TarHeader.TMAGIC);

    this.name = new StringBuffer();
    this.linkName = new StringBuffer();

    String user =
        System.getProperty("user.name", "");

    if (user.length() > 31) {
      user = user.substring(0, 31);

    }
    this.userId = 0;
    this.groupId = 0;
    this.userName = new StringBuffer(user);
    this.groupName = new StringBuffer("");
  }

  /**
   * TarHeaders can be cloned.
   */
  public Object
      clone() {
    TarHeader hdr = null;

    try {
      hdr = (TarHeader)super.clone();

      hdr.name =
          (this.name == null) ? null
          : new StringBuffer(this.name.toString());
      hdr.mode = this.mode;
      hdr.userId = this.userId;
      hdr.groupId = this.groupId;
      hdr.size = this.size;
      hdr.modTime = this.modTime;
      hdr.checkSum = this.checkSum;
      hdr.linkFlag = this.linkFlag;
      hdr.linkName =
          (this.linkName == null) ? null
          : new StringBuffer(this.linkName.toString());
      hdr.magic =
          (this.magic == null) ? null
          : new StringBuffer(this.magic.toString());
      hdr.userName =
          (this.userName == null) ? null
          : new StringBuffer(this.userName.toString());
      hdr.groupName =
          (this.groupName == null) ? null
          : new StringBuffer(this.groupName.toString());
      hdr.devMajor = this.devMajor;
      hdr.devMinor = this.devMinor;
    }
    catch (CloneNotSupportedException ex) {
      ex.printStackTrace(System.err);
    }

    return hdr;
  }

  /**
   * Get the name of this entry.
   *
   * @return Teh entry's name.
   */
  public String
      getName() {
    return this.name.toString();
  }

  /**
   * Parse an octal string from a header buffer. This is used for the
   * file permission mode value.
   *
   * @param header The header buffer from which to parse.
   * @param offset The offset into the buffer from which to parse.
   * @param length The number of header bytes to parse.
   * @return The long value of the octal string.
   */
  public static long
      parseOctal(byte[] header, int offset, int length) throws
      InvalidHeaderException {
    long result = 0;
    boolean stillPadding = true;

    int end = offset + length;
    for (int i = offset; i < end; ++i) {
      if (header[i] == 0) {
        break;
      }

      if (header[i] == (byte) ' ' || header[i] == '0') {
        if (stillPadding) {
          continue;
        }

        if (header[i] == (byte) ' ') {
          break;
        }
      }

      stillPadding = false;

      result =
          (result << 3)
          + (header[i] - '0');
    }

    return result;
  }

  /**
   * Parse a file name from a header buffer. This is different from
   * parseName() in that is recognizes 'ustar' names and will handle
   * adding on the "prefix" field to the name.
   *
   * Contributed by Dmitri Tikhonov <dxt2431@yahoo.com>
   *
   * @param header The header buffer from which to parse.
   * @param offset The offset into the buffer from which to parse.
   * @param length The number of header bytes to parse.
   * @return The header's entry name.
   */
  public static StringBuffer
      parseFileName(byte[] header) {
    StringBuffer result = new StringBuffer(256);

    // If header[345] is not equal to zero, then it is the "prefix"
    // that 'ustar' defines. It must be prepended to the "normal"
    // name field. We are responsible for the separating '/'.
    //
    if (header[345] != 0) {
      for (int i = 345; i < 500 && header[i] != 0; ++i) {
        result.append( (char) header[i]);
      }

      result.append("/");
    }

    for (int i = 0; i < 100 && header[i] != 0; ++i) {
      result.append( (char) header[i]);
    }

    return result;
  }

  /**
   * Parse an entry name from a header buffer.
   *
   * @param header The header buffer from which to parse.
   * @param offset The offset into the buffer from which to parse.
   * @param length The number of header bytes to parse.
   * @return The header's entry name.
   */
  public static StringBuffer
      parseName(byte[] header, int offset, int length) throws
      InvalidHeaderException {
    StringBuffer result = new StringBuffer(length);

    int end = offset + length;
    for (int i = offset; i < end; ++i) {
      if (header[i] == 0) {
        break;
      }
      result.append( (char) header[i]);
    }

    return result;
  }

  /**
   * This method, like getNameBytes(), is intended to place a name
   * into a TarHeader's buffer. However, this method is sophisticated
   * enough to recognize long names (name.length() > NAMELEN). In these
   * cases, the method will break the name into a prefix and suffix and
   * place the name in the header in 'ustar' format. It is up to the
   * TarEntry to manage the "entry header format". This method assumes
   * the name is valid for the type of archive being generated.
   *
   * @param outbuf The buffer containing the entry header to modify.
   * @param newName The new name to place into the header buffer.
   * @return The current offset in the tar header (always TarHeader.NAMELEN).
   * @throws InvalidHeaderException If the name will not fit in the header.
   */
  public static int
      getFileNameBytes(String newName, byte[] outbuf) throws
      InvalidHeaderException {
    if (newName.length() > 100) {
      // Locate a pathname "break" prior to the maximum name length...
      int index = newName.indexOf("/", newName.length() - 100);
      if (index == -1) {
        throw new InvalidHeaderException
            ("file name is greater than 100 characters, " + newName);
      }

      // Get the "suffix subpath" of the name.
      String name = newName.substring(index + 1);

      // Get the "prefix subpath", or "prefix", of the name.
      String prefix = newName.substring(0, index);
      if (prefix.length() > TarHeader.PREFIXLEN) {
        throw new InvalidHeaderException
            ("file prefix is greater than 155 characters");
      }

      TarHeader.getNameBytes
          (new StringBuffer(name), outbuf,
           TarHeader.NAMEOFFSET, TarHeader.NAMELEN);

      TarHeader.getNameBytes
          (new StringBuffer(prefix), outbuf,
           TarHeader.PREFIXOFFSET, TarHeader.PREFIXLEN);
    }
    else {
      TarHeader.getNameBytes
          (new StringBuffer(newName), outbuf,
           TarHeader.NAMEOFFSET, TarHeader.NAMELEN);
    }

    // The offset, regardless of the format, is now the end of the
    // original name field.
    //
    return TarHeader.NAMELEN;
  }

  /**
   * Move the bytes from the name StringBuffer into the header's buffer.
   *
   * @param header The header buffer into which to copy the name.
   * @param offset The offset into the buffer at which to store.
   * @param length The number of header bytes to store.
   * @return The new offset (offset + length).
   */
  public static int
      getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
    int i;

    for (i = 0; i < length && i < name.length(); ++i) {
      buf[offset + i] = (byte) name.charAt(i);
    }

    for (; i < length; ++i) {
      buf[offset + i] = 0;
    }

    return offset + length;
  }

  /**
   * Parse an octal integer from a header buffer.
   *
   * @param header The header buffer from which to parse.
   * @param offset The offset into the buffer from which to parse.
   * @param length The number of header bytes to parse.
   * @return The integer value of the octal bytes.
   */
  public static int
      getOctalBytes(long value, byte[] buf, int offset, int length) {
    byte[] result = new byte[length];

    int idx = length - 1;

    buf[offset + idx] = 0;
    --idx;
    buf[offset + idx] = (byte) ' ';
    --idx;

    if (value == 0) {
      buf[offset + idx] = (byte) '0';
      --idx;
    }
    else {
      for (long val = value; idx >= 0 && val > 0; --idx) {
        buf[offset + idx] = (byte)
            ( (byte) '0' + (byte) (val & 7));
        val = val >> 3;
      }
    }

    for (; idx >= 0; --idx) {
      buf[offset + idx] = (byte) ' ';
    }

    return offset + length;
  }

  /**
   * Parse an octal long integer from a header buffer.
   *
   * @param header The header buffer from which to parse.
   * @param offset The offset into the buffer from which to parse.
   * @param length The number of header bytes to parse.
   * @return The long value of the octal bytes.
   */
  public static int
      getLongOctalBytes(long value, byte[] buf, int offset, int length) {
    byte[] temp = new byte[length + 1];
    TarHeader.getOctalBytes(value, temp, 0, length + 1);
    System.arraycopy(temp, 0, buf, offset, length);
    return offset + length;
  }

  /**
   * Parse the checksum octal integer from a header buffer.
   *
   * @param header The header buffer from which to parse.
   * @param offset The offset into the buffer from which to parse.
   * @param length The number of header bytes to parse.
   * @return The integer value of the entry's checksum.
   */
  public static int
      getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
    TarHeader.getOctalBytes(value, buf, offset, length);
    buf[offset + length - 1] = (byte) ' ';
    buf[offset + length - 2] = 0;
    return offset + length;
  }

}
